using System;
using System.Collections.Generic;
using System.Xml;

namespace ExtremeSwank.Authentication.OpenID.Plugins.Discovery
{
    /// <summary>
    /// XRDS Discovery Plugin.  Provides everything needed to
    /// discover OpenIDs using XRDS documents.
    /// </summary>
    [Serializable]
    public class XRDS : IDiscovery
    {
        const string _Name = "XRDS Discovery Plugin";
        ProtocolVersion _PV;
        private StateContainer _Parent;

        /// <summary>
        /// Gets the name of this discovery plugin.
        /// </summary>
        /// <remarks>Always returns "XRDS Discovery Plugin".</remarks>
        public string Name
        {
            get
            {
                return _Name;
            }
        }
        /// <summary>
        /// Gets or sets the parent StateContainer object.
        /// </summary>
        public StateContainer Parent
        {
            get { return _Parent; }
            set { _Parent = value; }
        }

        private static readonly string[] Prefixes = { "=", "@", "+", "$", "!", "xri://" };
        private static readonly string[] IgnorePrefixes = { "xri://$dns*", "xri://$ip*" };
        /// <summary>
        /// Accepts a claimed identifier and returns
        /// the normalized identifier, and an end-point URL.
        /// </summary>
        /// <param name="openid">String containing claimed identifier.</param>
        /// <returns>Returns a string array with two fields, first field contains a normalized version
        /// of the claimed identifier, and an end-point URL.  Returns null if this plugin does not support
        /// the identifier type.</returns>
        public string[] ProcessID(string openid)
        {
            string tid = openid;
            string url_id = "";

            foreach (string prefix in Prefixes)
            {
                if (tid.StartsWith(prefix))
                {
                    if (prefix.Length == 1)
                    {
                        url_id = "http://xri.net/" + tid;
                    }
                    else
                    {
                        string a = tid;
                        bool ignore = false;
                        foreach (string ip in IgnorePrefixes)
                        {
                            if (a.StartsWith(ip))
                            {
                                ignore = true;
                            }
                        }
                        if (!ignore)
                        {
                            a = a.Substring(prefix.Length, a.Length - prefix.Length);
                            tid = a;
                            url_id = "https://xri.net/" + tid;
                        }
                    }
                }
            }
            if (url_id == "")
            {
                return null;
            }
            string[] ret = { tid, url_id };
            return ret;
        }
        /// <summary>
        /// Parse HTTP response for OpenID Identity Providers.
        /// </summary>
        /// <param name="content">HTTP response content to parse.</param>
        /// <returns>String array containing two string arrays, first lists server endpoints, the second
        /// lists local identifiers (delegates).</returns>
        /// <remarks><para>In the two arrays, each of the discovered entry indexes are numerically matched.  
        /// An OpenID Server in the first array at index 1 will have its matching discovered delegate
        /// at index 1 in the second array.</para>
        /// <para>
        /// For instance, In cases where
        /// an OpenID Server endpoint is discovered without a Delegate ID, the matching index in the 
        /// second array will be set to null.
        /// </para>
        /// </remarks>
        public string[][] Discover(string content)
        {
            if (!content.Contains("<?xml"))
            {
                return null;
            }
            int xmlbegin = content.IndexOf("<?xml");
            string fixedcontent = content.Substring(xmlbegin);

            XmlDocument xd = new XmlDocument();
            try
            {
                xd.LoadXml(fixedcontent);
            }
            catch
            {
                Tracer.Write("XML decode failed!");
                return null;
            }

            foreach (IExtension ext in Parent.ExtensionPlugins)
            {
                if (ext is IXRDSConsumer)
                {
                    IXRDSConsumer ixc = (IXRDSConsumer)ext;
                    ixc.ProcessXRDS(xd);
                }
            }

            XmlNamespaceManager nsmanager = new XmlNamespaceManager(xd.NameTable);
            nsmanager.AddNamespace("openid", "http://openid.net/xmlns/1.0");
            nsmanager.AddNamespace("xrds", "xri://$xrds");
            nsmanager.AddNamespace("xrd", "xri://$xrd*($v*2.0)");

            XmlNode rootnode = xd.DocumentElement;

            List<DiscoveryEntry> entries = new List<DiscoveryEntry>();

            List<string> servers = new List<string>();
            List<string> delegates = new List<string>();

            XmlNodeList xmlServices = rootnode.SelectNodes("/xrds:XRDS/xrd:XRD/xrd:Service", nsmanager);
            foreach (XmlNode node in xmlServices)
            {
                DiscoveryEntry entry = new DiscoveryEntry(null, null, 0, ProtocolVersion.Invalid);

                foreach (XmlAttribute attr in node.Attributes)
                {
                    switch (attr.Name.ToLower())
                    {
                        case "priority":
                            entry.Priority = Convert.ToInt32(attr.Value);
                            break;
                        default:
                            break;
                    }
                }

                foreach (XmlNode servicenode in node.ChildNodes)
                {
                    string val = null;
                    if (servicenode.HasChildNodes)
                    {
                        val = servicenode.ChildNodes[0].Value;
                    }
                    if (val != null)
                    {
                        switch (servicenode.Name)
                        {
                            case "Type":
                                if (val.Contains("http://specs.openid.net/auth/2."))
                                {
                                    entry.MaxVersion = ProtocolVersion.V2_0;
                                }
                                else if (val.Contains("http://openid.net/signon/1."))
                                {
                                    if (entry.MaxVersion != ProtocolVersion.V2_0)
                                    {
                                        entry.MaxVersion = ProtocolVersion.V1_1;
                                    }
                                }
                                break;
                            case "URI":
                                entry.OpenIDServer = val;
                                break;
                            case "openid:Delegate":
                                entry.DelegateID = val;
                                break;
                            case "LocalID":
                                entry.DelegateID = val;
                                break;
                            default:
                                break;
                        }
                    }
                }
                if (!String.IsNullOrEmpty(entry.OpenIDServer) && entry.MaxVersion != ProtocolVersion.Invalid)
                {
                    entries.Add(entry);
                }
            }

            DiscoveryEntryComparer comp = new DiscoveryEntryComparer();
            entries.Sort(comp);

            int i = 0;
            foreach (DiscoveryEntry entry in entries)
            {
                if (i == 0)
                {
                    _PV = entry.MaxVersion;
                }
                servers.Add(entry.OpenIDServer);
                delegates.Add(entry.DelegateID);
                i++;
            }

            List<string[]> ret = new List<string[]>();
            if (servers.Count != 0)
            {
                ret.Add(servers.ToArray());
                ret.Add(delegates.ToArray());
                return ret.ToArray();
            }
            return null;
        }
        /// <summary>
        /// Gets the newest OpenID protocol version supported
        /// by the Identity Provider.
        /// </summary>
        public ProtocolVersion Version
        {
            get { return _PV; }
        }
        /// <summary>
        /// Creates is XRDS discovery plugin, automatically registers it
        /// with the specified StateContainer object.
        /// </summary>
        /// <param name="state">Parent StateContainer object</param>
        public XRDS(StateContainer state)
        {
            Parent = state;
            state.RegisterPlugin(this);
        }

        /// <summary>
        /// Creates is XRDS discovery plugin, automatically registers it
        /// with the specified OpenIDConsumer object.
        /// </summary>
        /// <param name="openid">Parent OpenIDConsumer object</param>
        public XRDS(OpenIDConsumer openid)
        {
            Parent = openid.StateContainer;
            Parent.RegisterPlugin(this);
        }
    }

}
